﻿using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using Org.BouncyCastle.Tls;
using SiemensPLC.UAClientHelper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Collections.Specialized.BitVector32;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Button;
using Opc.Ua.Security.Certificates;
using Newtonsoft.Json.Linq;
using System.Reflection;
using System.Diagnostics.Metrics;

namespace OPCUA
{

    public class plcComm
    {

        private class plcItem
        {

            public int plcid { get; set; }
            public string plcip { get; set; }
            public string nodeid { get; set; }
            public string tag { get; set; }
            public string name { get; set; }
            public string svalue { get; set; }
            public string datatype { get; set; }
            public DateTime updatetime { get; set; }
            public int deviceid { get; set; }
            public int lineid { get; set; }
            public int groupid { get; set; }

        }


        private static plcItem[] plcItems = new plcItem[0];

        private static plcItem[][] PLCS =null;

        public static bool start;

        public static double[] totalms = { }; //PLC读取所用时间 毫秒

        private static Session[] client = new Session[0];

        private static SessionReconnectHandler[] reconnectHandler = new SessionReconnectHandler[0];

        private static string[] currentSessionMessage = new string[0];

        private static bool[] isConnected = new bool[0];
        private static bool[] isloaded = new bool[0];

        delegate void connectCallBack(bool success, string msg);

        public plcComm()

        {
            start = true;

            sql.openResult items = sql.open("select *  from plc  order by plcid ");  //所有plcItems数据集

            sql.openResult plcs = sql.open("select deviceid, plcip from plc  group by deviceid, plcip  order by deviceid");  //需要连接的plcs

            if (!items.successed) {
                MessageBox.Show("数据库连接失败");
                return;
            }

            if (!plcs.successed)
            {
                MessageBox.Show("PLC连接失败");
                return;
            }

            int cnt = plcs.dataTable.Rows.Count;

            if (cnt == 0)
            {
                //没有需要连接的PLC
                return;
            }



            Array.Resize<Session>(ref client, cnt);
            Array.Resize<SessionReconnectHandler>(ref reconnectHandler, cnt);
            Array.Resize<bool>(ref isConnected, cnt);
            Array.Resize<bool>(ref isloaded, cnt);
            Array.Resize<string>(ref currentSessionMessage, cnt);
            Array.Resize<double>(ref totalms, cnt);

            Array.Resize<plcItem>(ref plcItems, items.dataTable.Rows.Count);

            DataTable dt = items.dataTable;

            for (int i = 0; i < dt.Rows.Count; i++)
            {
                plcItem plcItem = new plcItem();

                plcItem.plcid = int.Parse(dt.Rows[i]["plcid"].ToString());
                plcItem.plcip = dt.Rows[i]["plcip"].ToString();
                plcItem.nodeid = dt.Rows[i]["nodeid"].ToString();
                plcItem.tag = dt.Rows[i]["tag"].ToString();
                plcItem.name = dt.Rows[i]["name"].ToString();
                plcItem.svalue = dt.Rows[i]["svalue"].ToString();
                plcItem.deviceid = int.Parse(dt.Rows[i]["deviceid"].ToString());
                plcItem.lineid = int.Parse(dt.Rows[i]["lineid"].ToString());
                plcItem.groupid = int.Parse(dt.Rows[i]["groupid"].ToString());
                plcItem.updatetime = DateTime.Now;
                plcItem.datatype = "";

                plcItems[i] = plcItem;
            }
            
            //建立连接
            foreach (DataRow dr in plcs.dataTable.Rows)    
            {
                int deviceid = int.Parse(dr["deviceid"].ToString());
                string addr = dr["plcip"].ToString();

                currentSessionMessage[deviceid] = "正在初始化连接";
                // 设置20S连接超时,用于访问服务器的用户名跟密码   serverUrl = "opc.tcp://192.168.31.199:4840";
                initClient(deviceid, addr, 20000, "admin", "wizard", (success, msg) =>
                {
                    if (success)
                    {
                        currentSessionMessage[deviceid] = "连接成功";
                        isConnected[deviceid]= true;
                    }
                    else
                    {
                        currentSessionMessage[deviceid] = "连接失败： " + msg;
                        isConnected[deviceid]= false;

                        MessageBox.Show("PLC" + (deviceid+1).ToString() + "初始化连接失败");
                    }
                });

            }


            PLCS = new plcItem[cnt][];

            for (int ix = 0; ix < cnt; ix++)
            {

                isloaded[ix] = false;

                PLCS[ix] = plcItems.Where(item => item.deviceid == ix).ToArray(); 

                int index = ix;

                new System.Threading.Thread( ()=>{
                     while (start)
                     {

                        if (isConnected[index]) {

                            if (!isloaded[index])
                            {
                                isloaded[index] = readOnce(index);  //读一遍变量类型
                            }
                            else
                            {
                                readLoop(index);   //批量读变量值
                            }
                              
                        }
                        else
                        {
                            totalms[index] = -1;
                        }

                        System.Threading.Thread.Sleep(300);
                     }

                 }).Start(); //读取数据到后台
            }


        }



        private bool readOnce( int index )
        {

            totalms[index] = -1;

            DateTime startdate = System.DateTime.Now;

            foreach (plcItem node in PLCS[index])
            {
               

                NodeId nodeId = new NodeId(node.nodeid);
                DataValue dataValue = new DataValue();
                try
                {
                    dataValue = client[index].ReadValue(nodeId);
                }
                catch (Exception e)
                {
                    return false;
                }
                string value = ChangeType(dataValue, dataValue.Value).ToString();
                string type = getDataTypeStr(dataValue);
                if (!sql.exec("update plc set svalue='" + value + "', datatype='"+type+"', updatetime=getdate() where plcid=" + node.plcid.ToString() ))
                    return false;

                plcItems[node.plcid].datatype = type;
                plcItems[node.plcid].svalue = value;
                plcItems[node.plcid].updatetime = DateTime.Now;
            }

            TimeSpan ts = System.DateTime.Now - startdate;
            totalms[index] = ts.Milliseconds;

            return true;

        }


        private void readLoop(int index)
        {


            DateTime startdate = System.DateTime.Now;

            List<String> itemsToRead = new List<String>();

            foreach (plcItem node in PLCS[index])
            {
                itemsToRead.Add(node.nodeid);
            }
            //------------------------  如果单个PLC变量过多，需要分批处理        --------------------------

            plcOperateResult res = ReadValuesById(index, itemsToRead);  //读取

            if (res.successed)
            {
                //解析读取到的结果
                for (int ii = 0; ii < itemsToRead.Count; ii++)
                {
                    int plcid = PLCS[index][ii].plcid;

                    string value = res.result[ii];

                    value = ChangeType(plcItems[plcid].datatype, value).ToString();





                    if (plcItems[plcid].svalue != value) //与缓存的值不相同，更新
                    {
                        if (sql.exec("update plc set svalue='" + value + "', updatetime=getdate() where plcid=" + plcid.ToString()))
                        {
                            plcItems[plcid].svalue = value;
                            plcItems[plcid].updatetime = DateTime.Now;
                        }
                    }
                    else
                    {
                        plcItems[plcid].updatetime = DateTime.Now;
                    }

                }

                TimeSpan ts = System.DateTime.Now - startdate;
                totalms[index] = ts.Milliseconds;

            }
            else
            {
                totalms[index] = -1;
            }

        }


        private async void initClient(int index, string serverUrl, uint sessionTimeout, string username, string password, connectCallBack callBack)
        {
            ApplicationInstance app = new ApplicationInstance();

            app.ApplicationType = Opc.Ua.ApplicationType.Client;

            app.ConfigSectionName = "Client" ;

            try
            {

                var config = await CreateClientConfiguration();  //加载配置


                app.CheckApplicationInstanceCertificate(false, 0).Wait();

                config.CertificateValidator.CertificateValidation += certificateValidation; //验证服务器证书

                var endpointDescription = CoreClientUtils.SelectEndpoint(config, serverUrl, true, 15000);    //设置15S连接超时
                var endpointConfiguration = EndpointConfiguration.Create(config);
                var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

                if (username == null)
                {
                    client[index] = await Session.Create(config, endpoint,
                        false, true,
                        config.ApplicationName,
                        sessionTimeout,
                        null, null);
                }
                else    //连接设置需要用户名跟密码
                {
                    client[index] = await Session.Create(config, endpoint,
                        false, true,
                        config.ApplicationName,
                        sessionTimeout,
                        new UserIdentity(username, password), null);

                }




                if (client[index] == null)
                {
                    callBack(false, "连接超时");
                    return;
                }

            }
            catch (Exception ex)
            {
                callBack(false, ex.Message);
                return;
            }



            client[index].KeepAlive += sessionKeepAlive;

            callBack(true, null);//成功连接到OPC服务器


        }


        private int getSessionIndex(ISession session)
        {
            for(int i = 0; i<client.Length;i++)
            {
                if (client[i]==null)
                {
                    continue;
                }

                if(ReferenceEquals(session, client[i]))
                    return i;
            }
            return -1;
        }
       
        //通信保持与重连
        private void sessionKeepAlive(ISession session, KeepAliveEventArgs e)
        {

            int index = getSessionIndex(session);
            if (index == -1) return;

            if (e.Status != null && ServiceResult.IsBad(e.Status))
            {


                if (reconnectHandler[index] == null)
                {
                    reconnectHandler[index] = new SessionReconnectHandler(true, 10 * 1000); //重连
                }


                currentSessionMessage[index]="正在尝试重新连接";

                reconnectHandler[index].BeginReconnect(session, 1000, ( sender,  ev) =>
                {
                    //重连成功
                    currentSessionMessage[index] = "连接成功";

                    if (reconnectHandler[index].Session == null) return;

                    if (!ReferenceEquals(session, reconnectHandler[index].Session))
                    {
                        var oldSession = session;
                        oldSession.KeepAlive -= sessionKeepAlive;

                        client[index] = reconnectHandler[index].Session as Session;
                        client[index].KeepAlive += sessionKeepAlive;

                        Utils.SilentDispose(oldSession);
                    }
                });
            }
        }




        private static void certificateValidation(CertificateValidator sender, CertificateValidationEventArgs e) //接受服务器证书
        {
            e.Accept = true;
        }

        private static async Task<ApplicationConfiguration> CreateClientConfiguration()
        {
            // The application configuration can be loaded from any file.
            // ApplicationConfiguration.Load() method loads configuration by looking up a file path in the App.config.
            // This approach allows applications to share configuration files and to update them.
            // This example creates a minimum ApplicationConfiguration using its default constructor.
            ApplicationConfiguration configuration = new ApplicationConfiguration();

            // Step 1 - Specify the client identity.
            configuration.ApplicationName = "UA Client 1500";
            configuration.ApplicationType = ApplicationType.Client;
            configuration.ApplicationUri = "urn:MyClient"; //Kepp this syntax
            configuration.ProductUri = "SiemensAG.IndustryOnlineSupport";

            // Step 2 - Specify the client's application instance certificate.
            // Application instance certificates must be placed in a windows certficate store because that is 
            // the best way to protect the private key. Certificates in a store are identified with 4 parameters:
            // StoreLocation, StoreName, SubjectName and Thumbprint.
            // When using StoreType = Directory you need to have the opc.ua.certificategenerator.exe installed on your machine

            configuration.SecurityConfiguration = new SecurityConfiguration();
            configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier();
            configuration.SecurityConfiguration.ApplicationCertificate.StoreType = CertificateStoreType.X509Store;
            configuration.SecurityConfiguration.ApplicationCertificate.StorePath = "CurrentUser\\My";
            configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = configuration.ApplicationName;

            // Define trusted root store for server certificate checks
            configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = CertificateStoreType.X509Store;
            configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = "CurrentUser\\Root";
            configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = CertificateStoreType.X509Store;
            configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = "CurrentUser\\Root";

            // find the client certificate in the store.
            X509Certificate2 clientCertificate = await configuration.SecurityConfiguration.ApplicationCertificate.Find(true);

            // create a new self signed certificate if not found.
            if (clientCertificate == null)
            {
                // Get local interface ip addresses and DNS name
                List<string> localIps = GetLocalIpAddressAndDns();

                UInt16 keySize = 2048; //must be multiples of 1024
                UInt16 lifeTime = 24; //in month
                UInt16 algorithm = 1; //0 = SHA1; 1 = SHA256

                // this code would normally be called as part of the installer - called here to illustrate.
                // create a new certificate an place it in the current user certificate store.
                clientCertificate = CertificateFactory.CreateCertificate(
                    configuration.SecurityConfiguration.ApplicationCertificate.StoreType,
                    configuration.SecurityConfiguration.ApplicationCertificate.StorePath,
                    "pwd",
                    configuration.ApplicationUri,
                    configuration.ApplicationName,
                    null,
                    localIps,
                    keySize,
                    DateTime.Now,
                    lifeTime,
                    algorithm);

            }


            // Step 3 - Specify the supported transport quotas.
            // The transport quotas are used to set limits on the contents of messages and are
            // used to protect against DOS attacks and rogue clients. They should be set to
            // reasonable values.
            configuration.TransportQuotas = new TransportQuotas();
            configuration.TransportQuotas.OperationTimeout = 360000;
            configuration.TransportQuotas.MaxStringLength = 67108864;
            configuration.TransportQuotas.MaxByteStringLength = 16777216; //Needed, i.e. for large TypeDictionarys


            // Step 4 - Specify the client specific configuration.
            configuration.ClientConfiguration = new ClientConfiguration();
            configuration.ClientConfiguration.DefaultSessionTimeout = 360000;

            // Step 5 - Validate the configuration.
            // This step checks if the configuration is consistent and assigns a few internal variables
            // that are used by the SDK. This is called automatically if the configuration is loaded from
            // a file using the ApplicationConfiguration.Load() method.
            await configuration.Validate(ApplicationType.Client);

            return configuration;
        }




        private static List<string> GetLocalIpAddressAndDns()
        {
            List<string> localIps = new List<string>();
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIps.Add(ip.ToString());
                }
            }
            if (localIps.Count == 0)
            {
                throw new Exception("Local IP Address Not Found!");
            }
            localIps.Add(Dns.GetHostName());
            return localIps;
        }





        #region 读写
        public class plcOperateResult
        {
            public bool successed { get; set; }
            public string errorMsg { get; set; }
            public List<string> result { get; set; } 
            public List<string> resultType { get; set; }

        }



        public plcOperateResult ReadValuesById(int index, List<String> nodeIdStrings)
        {
            plcOperateResult operateResult = new plcOperateResult(); 

            List<NodeId> nodeIds = new List<NodeId>();
            List<Type> types = new List<Type>();
            List<object> values = new List<object>();
            List<ServiceResult> serviceResults = new List<ServiceResult>();
            foreach (string str in nodeIdStrings)
            {
                nodeIds.Add(new NodeId(str));
                types.Add(null);
            }
            try
            {
                //Read the dataValues
                client[index].ReadValues(nodeIds, types, out values, out serviceResults);

                //check ServiceResults 
                foreach (ServiceResult svResult in serviceResults)
                {
                    if (svResult.ToString() != "Good")
                    {
                        operateResult.successed = false;
                        operateResult.errorMsg="读取结果失败： "+ svResult.ToString();
                        return operateResult;
                    }
                }

                List<string> resultStrings = new List<string>();
                List<string> resultTypes = new List<string>();
                foreach (object result in values)
                {
                    if (result != null)
                    {
                        resultStrings.Add(result.ToString());
                    }
                    else
                    {
                        resultStrings.Add("(null)");
                    }
                }

                operateResult.successed = true;
                operateResult.result = resultStrings;
                operateResult.resultType = resultTypes;
                return operateResult;

            }
            catch (Exception e)
            {
                operateResult.successed = false;
                operateResult.errorMsg = "读取失败： " + e.Message;
                return operateResult;
            }
        }



        private string getDataTypeStr(DataValue currentValue)
        {
            if (currentValue == null) return "";  //无法得知类型

            string result = "";
            try { return currentValue.WrappedValue.TypeInfo.BuiltInType.ToString(); }
            catch
            {
                return "";
            }

        }





        public void WriteValuesById(int index,   List<String> values, List<String> nodeIdStrings)
        {
            //Create a collection of values to write
            WriteValueCollection valuesToWrite = new WriteValueCollection();
            //Create a collection for StatusCodes
            StatusCodeCollection result = new StatusCodeCollection();
            //Create a collection for DiagnosticInfos
            DiagnosticInfoCollection diagnostics = new DiagnosticInfoCollection();

            foreach (String str in nodeIdStrings)
            {
                //Create a nodeId
                NodeId nodeId = new NodeId(str);
                //Create a dataValue
                DataValue dataValue = new DataValue();
                //Read the dataValue
                try
                {
                    dataValue = client[index].ReadValue(nodeId);
                }
                catch (Exception e)
                {
                    //handle Exception here
                    throw e;
                }

                string test = dataValue.Value.GetType().Name;
                //Get the data type of the read dataValue
                //Handle Arrays here: TBD
                Variant variant = 0;
                try
                {
                    variant = new Variant(Convert.ChangeType(values[nodeIdStrings.IndexOf(str)], dataValue.Value.GetType()));
                }
                catch //no base data type
                {
                    //Handle different arrays types here: TBD
                    if (dataValue.Value.GetType().Name == "string[]")
                    {
                        string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';');
                        variant = new Variant(arrString);
                    }
                    else if (dataValue.Value.GetType().Name == "Byte[]")
                    {
                        string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';');
                        Byte[] arrInt = new Byte[arrString.Length];

                        for (int i = 0; i < arrString.Length; i++)
                        {
                            arrInt[i] = Convert.ToByte(arrString[i]);
                        }
                        variant = new Variant(arrInt);
                    }
                    else if (dataValue.Value.GetType().Name == "Int16[]")
                    {
                        string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';');
                        Int16[] arrInt = new Int16[arrString.Length];

                        for (int i = 0; i < arrString.Length; i++)
                        {
                            arrInt[i] = Convert.ToInt16(arrString[i]);
                        }
                        variant = new Variant(arrInt);
                    }
                }

                //Overwrite the dataValue with a new constructor using read dataType
                dataValue = new DataValue(variant);

                //Create a WriteValue using the NodeId, dataValue and attributeType
                WriteValue valueToWrite = new WriteValue();
                valueToWrite.Value = dataValue;
                valueToWrite.NodeId = nodeId;
                valueToWrite.AttributeId = Attributes.Value;

                //Add the dataValues to the collection
                valuesToWrite.Add(valueToWrite);
            }

            try
            {
                //Write the collection to the server
                client[index].Write(null, valuesToWrite, out result, out diagnostics);
                foreach (StatusCode code in result)
                {
                    if (code != 0)
                    {
                        Exception ex = new Exception(code.ToString());
                        throw ex;
                    }
                }
            }
            catch (Exception e)
            {
                //handle Exception here
                throw e;
            }
        }





        public object readByTag(int index, string tag)
        {
            if (client[index] == null) return null;
            ReferenceDescription valueRef = GetReference( index, tag);   //获取PLC的Tag对应的ID等信息
            if (valueRef == null) return null;  //未能找到此节点
            ReadValueId nodeToRead = new ReadValueId();
            nodeToRead.NodeId = (NodeId)valueRef.NodeId;  //{ns=4;i=18} 节点ID
            nodeToRead.AttributeId = Attributes.Value;

            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            nodesToRead.Add(nodeToRead);

            DataValueCollection readResults = null;
            DiagnosticInfoCollection readDiagnosticInfos = null;

            try
            {
                ResponseHeader readHeader = client[index].Read(null, 0, TimestampsToReturn.Neither, nodesToRead,
                    out readResults, out readDiagnosticInfos);
            }
            catch (Exception e)
            {
                return null;
            }
            ClientBase.ValidateResponse(readResults, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(readDiagnosticInfos, nodesToRead);

            return readResults[0];  //读取到的变量值
        }


        public object writeByTag(int index, string tag, string typeString, object value) //根据已知的数据类型，写入
        {
            if (client[index] == null) return null;
            ReferenceDescription valueRef = GetReference(index, tag);   //获取PLC的Tag对应的ID等信息
            if (valueRef == null) return null;  //未能找到此节点
            WriteValue valueToWrite = new WriteValue();
            valueToWrite.NodeId = (NodeId)valueRef.NodeId;
            valueToWrite.AttributeId = Attributes.Value;

            valueToWrite.Value.Value = ChangeType(typeString, value);

            valueToWrite.Value.StatusCode = StatusCodes.Good;
            valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
            valueToWrite.Value.SourceTimestamp = DateTime.MinValue;

            WriteValueCollection valuesToWrite = new WriteValueCollection();
            valuesToWrite.Add(valueToWrite);

            StatusCodeCollection writeResults = null;
            DiagnosticInfoCollection writeDiagnosticInfos = null;

            try
            {
                ResponseHeader writeHeader = client[index].Write(null, valuesToWrite, out writeResults, out writeDiagnosticInfos);
            }
            catch (Exception e)
            {
                return null;
            }

            ClientBase.ValidateResponse(writeResults, valuesToWrite);
            ClientBase.ValidateDiagnosticInfos(writeDiagnosticInfos, valuesToWrite);

            return writeResults[0];
        }

        private static object ChangeType(string typeString, object value) //根据类型转换值，再写
        {
            object result = null;

            typeString = typeString.ToLower();

            switch (typeString)
            {
                case "boolean":
                    {
                        result = Convert.ToBoolean(value);
                        break;
                    }

                case "sbyte":
                    {
                        result = Convert.ToSByte(value);
                        break;
                    }

                case "byte":
                    {
                        result = Convert.ToByte(value);
                        break;
                    }

                case "int16":
                    {
                        result = Convert.ToInt16(value);
                        break;
                    }

                case "uint16":
                    {
                        result = Convert.ToUInt16(value);
                        break;
                    }

                case "int32":
                    {
                        result = Convert.ToInt32(value);
                        break;
                    }

                case "uint32":
                    {
                        result = Convert.ToUInt32(value);
                        break;
                    }

                case "float":
                    {
                        result = Convert.ToSingle(value);
                        break;
                    }


                case "bool":
                    {
                        result = Convert.ToBoolean(value);
                        break;
                    }

                case "int":
                    {
                        result = Convert.ToInt32(value);
                        break;
                    }

                case "string":  //中文乱码处理
                    {

                        byte[] cpBytes = Encoding.Unicode.GetBytes(Convert.ToString(value));

                        ArrayList arrayList = new ArrayList();
                        for (int jj = 0; jj < cpBytes.Length; jj++)
                        {
                            if ((jj % 2) == 0)
                                arrayList.Add(cpBytes[jj]);
                        }

                        result  =  Encoding.GetEncoding("GB18030").GetString((byte[])arrayList.ToArray(typeof(byte)));
                        break;
                    }

                case "int64":
                    {
                        result = Convert.ToInt64(value);
                        break;
                    }

                case "unt64":
                    {
                        result = Convert.ToUInt64(value);
                        break;
                    }

                case "double":
                    {
                        result = Convert.ToDouble(value);
                        break;
                    }

                default:
                    {
                        result = value;
                        break;
                    }
            }

            return result;
        }



        public object writeByTag(int index, string tag, object value) // 先读一遍，获取类型再写入，不推荐（速度慢）
        {
            if (client[index] == null) return null;
            ReferenceDescription valueRef = GetReference( index, tag);   //获取PLC的Tag对应的ID等信息
            if (valueRef == null) return null;  //未能找到此节点
            WriteValue valueToWrite = new WriteValue();
            valueToWrite.NodeId = (NodeId)valueRef.NodeId;
            valueToWrite.AttributeId = Attributes.Value;

            var currentValue = readByTag( index, tag) as DataValue;

            valueToWrite.Value.Value = ChangeType(currentValue, value);

            valueToWrite.Value.StatusCode = StatusCodes.Good;
            valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
            valueToWrite.Value.SourceTimestamp = DateTime.MinValue;

            WriteValueCollection valuesToWrite = new WriteValueCollection();
            valuesToWrite.Add(valueToWrite);

            StatusCodeCollection writeResults = null;
            DiagnosticInfoCollection writeDiagnosticInfos = null;

            try
            {
                ResponseHeader writeHeader = client[index].Write(null, valuesToWrite, out writeResults, out writeDiagnosticInfos);
            }
            catch (Exception e)
            {
                return null;
            }


            ClientBase.ValidateResponse(writeResults, valuesToWrite);
            ClientBase.ValidateDiagnosticInfos(writeDiagnosticInfos, valuesToWrite);

            return writeResults[0];
        }

        private static object ChangeType(DataValue currentValue, object value)
        {
            if (currentValue == null) return null;  //无法得知类型

            object result = currentValue.Value;


            switch (currentValue.WrappedValue.TypeInfo.BuiltInType)
            {
                case BuiltInType.Boolean:
                    {
                        result = Convert.ToBoolean(value);
                        break;
                    }

                case BuiltInType.SByte:
                    {
                        result = Convert.ToSByte(value);
                        break;
                    }

                case BuiltInType.Byte:
                    {
                        result = Convert.ToByte(value);
                        break;
                    }

                case BuiltInType.Int16:
                    {
                        result = Convert.ToInt16(value);
                        break;
                    }

                case BuiltInType.UInt16:
                    {
                        result = Convert.ToUInt16(value);
                        break;
                    }

                case BuiltInType.Int32:
                    {
                        result = Convert.ToInt32(value);
                        break;
                    }

                case BuiltInType.UInt32:
                    {
                        result = Convert.ToUInt32(value);
                        break;
                    }

                case BuiltInType.Int64:
                    {
                        result = Convert.ToInt64(value);
                        break;
                    }

                case BuiltInType.UInt64:
                    {
                        result = Convert.ToUInt64(value);
                        break;
                    }

                case BuiltInType.Float:
                    {
                        result = Convert.ToSingle(value);
                        break;
                    }

                case BuiltInType.Double:
                    {
                        result = Convert.ToDouble(value);
                        break;
                    }

                case BuiltInType.String:
                    {
                        result = Convert.ToString(value);
                        break;
                    }
                default:
                    {
                        result = value;
                        break;
                    }
            }

            return result;
        }


        private static ReferenceDescription GetReference(int index, string value) //逐级查找节点，找到对应描述
        {
            value = value.Trim();
            ReferenceDescription currentDescription = null;
            string[] values = value.Split('.');
            ReferenceDescriptionCollection collection = BrowseNode( index, ObjectIds.ObjectsFolder);

            if (collection == null) return null; //通讯失败

            for (int i = 0; i < values.Length; i++)
            {
                try
                {
                    currentDescription = collection.Where(item => item.DisplayName == values[i]).FirstOrDefault();
                    if (currentDescription == null) continue;
                    collection = BrowseNode( index, (NodeId)currentDescription.NodeId);
                }
                catch (Exception ex)
                {
                    return null;
                }
            }
            return currentDescription;
        }


        private static ReferenceDescriptionCollection BrowseNode(int index, NodeId nodeId)
        {
            BrowseDescription nodeToBrowse1 = new BrowseDescription();
            nodeToBrowse1.NodeId = nodeId;
            nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;
            nodeToBrowse1.IncludeSubtypes = true;
            nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
            nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescription nodeToBrowse2 = new BrowseDescription();
            nodeToBrowse2.NodeId = nodeId;
            nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;
            nodeToBrowse2.IncludeSubtypes = true;
            nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
            nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescriptionCollection nodeToBrowse = new BrowseDescriptionCollection();

            nodeToBrowse.Add(nodeToBrowse1);
            nodeToBrowse.Add(nodeToBrowse2);

            ReferenceDescriptionCollection references = Browse(index, nodeToBrowse, false);

            if (references != null)
                for (int i = 0; i < references.Count; i++)
                {
                    ReferenceDescription target = references[i];
                }

            return references;

        }


        public static ReferenceDescriptionCollection Browse(int index, BrowseDescriptionCollection nodesToBrowse, bool throwOnError)
        {
            try
            {
                ReferenceDescriptionCollection references = new ReferenceDescriptionCollection();
                BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection();

                while (nodesToBrowse.Count > 0)
                {
                    // start the browse operation.
                    BrowseResultCollection results = null;
                    DiagnosticInfoCollection diagnosticInfos = null;

                    client[index].Browse(
                        null,
                        null,
                        0,
                        nodesToBrowse,
                        out results,
                        out diagnosticInfos);

                    ClientBase.ValidateResponse(results, nodesToBrowse);
                    ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);

                    ByteStringCollection continuationPoints = new ByteStringCollection();

                    for (int ii = 0; ii < nodesToBrowse.Count; ii++)
                    {
                        // check for error.
                        if (StatusCode.IsBad(results[ii].StatusCode))
                        {
                            // this error indicates that the server does not have enough simultaneously active 
                            // continuation points. This request will need to be resent after the other operations
                            // have been completed and their continuation points released.
                            if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints)
                            {
                                unprocessedOperations.Add(nodesToBrowse[ii]);
                            }

                            continue;
                        }

                        // check if all references have been fetched.
                        if (results[ii].References.Count == 0)
                        {
                            continue;
                        }

                        // save results.
                        references.AddRange(results[ii].References);

                        // check for continuation point.
                        if (results[ii].ContinuationPoint != null)
                        {
                            continuationPoints.Add(results[ii].ContinuationPoint);
                        }
                    }

                    // process continuation points.
                    ByteStringCollection revisedContinuationPoints = new ByteStringCollection();

                    while (continuationPoints.Count > 0)
                    {
                        // continue browse operation.
                        client[index].BrowseNext(
                            null,
                            false,
                            continuationPoints,
                            out results,
                            out diagnosticInfos);

                        ClientBase.ValidateResponse(results, continuationPoints);
                        ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);

                        for (int ii = 0; ii < continuationPoints.Count; ii++)
                        {
                            // check for error.
                            if (StatusCode.IsBad(results[ii].StatusCode))
                            {
                                continue;
                            }

                            // check if all references have been fetched.
                            if (results[ii].References.Count == 0)
                            {
                                continue;
                            }

                            // save results.
                            references.AddRange(results[ii].References);

                            // check for continuation point.
                            if (results[ii].ContinuationPoint != null)
                            {
                                revisedContinuationPoints.Add(results[ii].ContinuationPoint);
                            }
                        }

                        // check if browsing must continue;
                        continuationPoints = revisedContinuationPoints;
                    }

                    // check if unprocessed results exist.
                    nodesToBrowse = unprocessedOperations;
                }

                // return complete list.
                return references;
            }
            catch (Exception exception)
            {
                if (throwOnError)
                {
                    throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError);
                }

                return null;
            }
        }

        #endregion
























    }



}
